
/* @internal */
namespace ts {
    export const enum ModuleInstanceState {
        NonInstantiated = 0,
        Instantiated = 1,
        ConstEnumOnly = 2
    }

    interface ActiveLabel {
        next: ActiveLabel | undefined;
        name: string;
        breakTarget: FlowLabel;
        continueTarget: FlowLabel | undefined;
        referenced: boolean;
    }

    const enum ContainerFlags {
        // The current node is not a container, and no container manipulation should happen before
        // recursing into it.
        None = 0,

        // The current node is a container.  It should be set as the current container (and block-
        // container) before recursing into it.  The current node does not have locals.  Examples:
        //
        //      Classes, ObjectLiterals, TypeLiterals, Interfaces...
        IsContainer = 1 << 0,

        // The current node is a block-scoped-container.  It should be set as the current block-
        // container before recursing into it.  Examples:
        //
        //      Blocks (when not parented by functions), Catch clauses, For/For-in/For-of statements...
        IsBlockScopedContainer = 1 << 1,

        // The current node is the container of a control flow path. The current control flow should
        // be saved and restored, and a new control flow initialized within the container.
        IsControlFlowContainer = 1 << 2,

        IsFunctionLike = 1 << 3,
        IsFunctionExpression = 1 << 4,
        HasLocals = 1 << 5,
        IsInterface = 1 << 6,
        IsObjectLiteralOrClassExpressionMethod = 1 << 7,
    }

    function initFlowNode<T extends FlowNode>(node: T) {
        return node;
    }

    const binder = createBinder();

    export function bindSourceFile(file: SourceFile, options: CompilerOptions) {
        tracing?.push(tracing.Phase.Bind, "bindSourceFile", { path: file.path }, /*separateBeginAndEnd*/ true);
        performance.mark("beforeBind");
        binder(file, options);
        performance.mark("afterBind");
        performance.measure("Bind", "beforeBind", "afterBind");
        tracing?.pop();
    }

    function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
        let file: SourceFile;
        let options: CompilerOptions;
        let parent: Node;
        let container: Node;
        let thisParentContainer: Node; // Container one level up
        let blockScopeContainer: Node;
        let lastContainer: Node;
        let seenThisKeyword: boolean;

        // state used by control flow analysis
        let currentFlow: FlowNode;
        let currentBreakTarget: FlowLabel | undefined;
        let currentContinueTarget: FlowLabel | undefined;
        let currentReturnTarget: FlowLabel | undefined;
        let currentExceptionTarget: FlowLabel | undefined;
        
        let activeLabelList: ActiveLabel | undefined;
        let hasExplicitReturn: boolean;

        // state used for emit helpers
        let emitFlags: NodeFlags;

        // If we are binding an assignment pattern, we will bind certain expressions differently.
        let inAssignmentPattern = false;

        let symbolCount = 0;

        let Symbol: new (flags: SymbolFlags, name: string) => Symbol;
        let classifiableNames: Set<string>;

        const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable };
        const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable };

        function createDiagnosticForNode(node: Node, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number): DiagnosticWithLocation {
            return createDiagnosticForNodeInSourceFile(getSourceFileOfNode(node) || file, node, message, arg0, arg1, arg2);
        }

        function bindSourceFile(f: SourceFile, opts: CompilerOptions) {
            file = f;
            options = opts;
            classifiableNames = new Set<string>();
            symbolCount = 0;

            Symbol = objectAllocator.getSymbolConstructor();


            if (!file.locals) {
                bind(file);
                file.symbolCount = symbolCount;
                file.classifiableNames = classifiableNames;
            }

            file = undefined!;
            options = undefined!;
            parent = undefined!;
            container = undefined!;
            lastContainer = undefined!;
            thisParentContainer = undefined!;
            blockScopeContainer = undefined!;
            seenThisKeyword = false;
            currentFlow = undefined!;
            currentBreakTarget = undefined;
            currentContinueTarget = undefined;
            currentReturnTarget = undefined;
            currentExceptionTarget = undefined;
            activeLabelList = undefined;
            hasExplicitReturn = false;
            inAssignmentPattern = false;
            emitFlags = NodeFlags.None;
        }

        return bindSourceFile;

        function createSymbol(flags: SymbolFlags, name: string): Symbol {
            symbolCount++;
            return new Symbol(flags, name);
        }

        function addDeclarationToSymbol(symbol: Symbol, node: Declaration, symbolFlags: SymbolFlags) {
            symbol.flags |= symbolFlags;

            node.symbol = symbol;
            symbol.declarations = appendIfUnique(symbol.declarations, node);
            if (!symbol.members) {
                symbol.members = createSymbolTable();
            }
            if (symbolFlags & SymbolFlags.Value) {
                setValueDeclaration(symbol, node);
            }
        }

        // Should not be called on a declaration with a computed property name,
        // unless it is a well known Symbol.
        function getDeclarationName(node: Declaration): string | undefined {
            const kind = node.kind;
            switch (kind) {
                case 语法.SourceFile:
                    return (<SourceFile>node).fileName;
            }
            return;
        }

        function getDisplayName(node: Declaration): string {
            return  declarationNameToString(node.name);
        }

        /**
         * Declares a Symbol for the node and adds it to symbols. Reports errors for conflicting identifier names.
         * @param symbolTable - The symbol table which node will be added to.
         * @param parent - node's parent declaration.
         * @param node - The declaration to be added to the symbol table
         * @param includes - The SymbolFlags that node has in addition to its declaration type (eg: export, ambient, etc.)
         * @param excludes - The flags which node cannot be declared alongside in a symbol table. Used to report forbidden declarations.
         */
        function declareSymbol(symbolTable: SymbolTable, parent: Symbol | undefined, node: Declaration, includes: SymbolFlags, excludes: SymbolFlags, isReplaceableByMethod?: boolean): Symbol {

            const name = getDeclarationName(node);

            let symbol: Symbol | undefined;
            if (name === undefined) {
                symbol = createSymbol(SymbolFlags.None, "<<Missing>>");
            }
            else {
                symbol = symbolTable.get(name);
                if (!symbol) {
                    symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name));
                    if (isReplaceableByMethod) symbol.isReplaceableByMethod = true;
                }
                else if (isReplaceableByMethod && !symbol.isReplaceableByMethod) {
                    return symbol;
                }
                else if (symbol.flags & excludes) {
                    if (symbol.isReplaceableByMethod) {
                        // Javascript constructor-declared symbols can be discarded in favor of
                        // prototype symbols like methods.
                        symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name));
                    }
                }
            }

            addDeclarationToSymbol(symbol, node, includes);
            if (symbol.parent) {
                Debug.assert(symbol.parent === parent, "Existing symbol parent should match new one");
            }
            else {
                symbol.parent = parent;
            }

            return symbol;
        }

        function bindContainer(node: Mutable<Node>, containerFlags: ContainerFlags) {
            const saveContainer = container;
            const saveThisParentContainer = thisParentContainer;
            const savedBlockScopeContainer = blockScopeContainer;
            if (containerFlags & ContainerFlags.IsContainer) {
                container = blockScopeContainer = node;
                if (containerFlags & ContainerFlags.HasLocals) {
                    container.locals = createSymbolTable();
                }
                addToContainerChain(container);
            }
            else if (containerFlags & ContainerFlags.IsBlockScopedContainer) {
                blockScopeContainer = node;
                blockScopeContainer.locals = undefined;
            }
            if (containerFlags & ContainerFlags.IsControlFlowContainer) {
                const saveCurrentFlow = currentFlow;
                const saveBreakTarget = currentBreakTarget;
                const saveContinueTarget = currentContinueTarget;
                const saveReturnTarget = currentReturnTarget;
                const saveExceptionTarget = currentExceptionTarget;
                const saveActiveLabelList = activeLabelList;
                currentFlow = initFlowNode({ flags: FlowFlags.Start });                    
                currentExceptionTarget = undefined;
                currentBreakTarget = undefined;
                currentContinueTarget = undefined;
                activeLabelList = undefined;
                hasExplicitReturn = false;
                bindChildren(node);
                // Reset all reachability check related flags on node (for incremental scenarios)
                node.flags &= ~NodeFlags.ReachabilityAndEmitFlags;
                if (!(currentFlow.flags & FlowFlags.Unreachable) && containerFlags & ContainerFlags.IsFunctionLike && nodeIsPresent((node as any).函数体 ? (node as any).函数体[0] : undefined)) {
                    node.flags |= NodeFlags.HasImplicitReturn;
                    if (hasExplicitReturn) node.flags |= NodeFlags.HasExplicitReturn;
                    (node as any).endFlowNode = currentFlow;
                }
                if (node.kind === 语法.SourceFile) {
                    node.flags |= emitFlags;
                    (node as any).endFlowNode = currentFlow;
                }

                if (currentReturnTarget) {
                    addAntecedent(currentReturnTarget, currentFlow);
                    currentFlow = finishFlowLabel(currentReturnTarget);
                }
                currentFlow = saveCurrentFlow;
                currentBreakTarget = saveBreakTarget;
                currentContinueTarget = saveContinueTarget;
                currentReturnTarget = saveReturnTarget;
                currentExceptionTarget = saveExceptionTarget;
                activeLabelList = saveActiveLabelList;
            }
            else if (containerFlags & ContainerFlags.IsInterface) {
                seenThisKeyword = false;
                bindChildren(node);
                node.flags = seenThisKeyword ? node.flags | NodeFlags.ContainsThis : node.flags & ~NodeFlags.ContainsThis;
            }
            else {
                bindChildren(node);
            }

            container = saveContainer;
            thisParentContainer = saveThisParentContainer;
            blockScopeContainer = savedBlockScopeContainer;
        }
        
        function addToContainerChain(next: Node) {
            if (lastContainer) {
                lastContainer.nextContainer = next;
            }

            lastContainer = next;
        }

        function bindEachFunctionsFirst(nodes: NodeArray<Node> | undefined): void {
            bindEach(nodes, n => n.kind === 语法.Def || n.kind === 语法.Defm ? bind(n) : undefined);
            bindEach(nodes, n => n.kind !== 语法.Def && n.kind !== 语法.Defm? bind(n) : undefined);
        }

        function bindEach(nodes: NodeArray<Node> | undefined, bindFunction: (node: Node) => void = bind): void {
            if (nodes === undefined) {
                return;
            }

            forEach(nodes, bindFunction);
        }

        function bindEachChild(node: Node) {
            forEachChild(node, bind, bindEach);
        }

        function bindChildren(node: Node): void {
            const saveInAssignmentPattern = inAssignmentPattern;
            // Most nodes aren't valid in an assignment pattern, so we clear the value here
            // and set it before we descend into nodes that could actually be part of an assignment pattern.
            inAssignmentPattern = false;
            if (checkUnreachable(node)) {
                bindEachChild(node);
                inAssignmentPattern = saveInAssignmentPattern;
                return;
            }
            
            inAssignmentPattern = saveInAssignmentPattern;
        }

        function setFlowNodeReferenced(flow: FlowNode) {
            // On first reference we set the Referenced flag, thereafter we set the Shared flag
            flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced;
        }

        function addAntecedent(label: FlowLabel, antecedent: FlowNode): void {
            if (!(antecedent.flags & FlowFlags.Unreachable) && !contains(label.antecedents, antecedent)) {
                (label.antecedents || (label.antecedents = [])).push(antecedent);
                setFlowNodeReferenced(antecedent);
            }
        }

        function finishFlowLabel(flow: FlowLabel): FlowNode {
            const antecedents = flow.antecedents;
            if (!antecedents) {
                return unreachableFlow;
            }
            if (antecedents.length === 1) {
                return antecedents[0];
            }
            return flow;
        }

        function bind(node: Node | undefined): void {
            if (!node) {
                return;
            }
            setParent(node, parent);
            bindWorker(node);
        }

        function bindWorker(node: Node) {
            node
        }

        function checkUnreachable(node: Node): boolean {
            node
            if (!(currentFlow.flags & FlowFlags.Unreachable)) {
                return false;
            }
            return true;
        }
    }
}
